package math3d

import (
	"fmt"
	"math/rand"
)

func Abs[T int | int32 | int64 | float32 | float64](a T) T {
	if a < 0 {
		return -a
	}

	return a
}

type V3 struct {
	V [3]int64
}

func NewV3(x, y, z int64) V3 {
	return V3{[3]int64{x, y, z}}
}

func (self V3) X() int64 {
	return self.V[0]
}
func (self V3) Y() int64 {
	return self.V[1]
}
func (self V3) Z() int64 {
	return self.V[2]
}

func (self *V3) SetX(v int64) {
	self.V[0] = v
}
func (self *V3) SetY(v int64) {
	self.V[1] = v
}
func (self *V3) SetZ(v int64) {
	self.V[2] = v
}

func (self V3) Sub(v3 V3) V3 {
	return NewV3(self.V[0]-v3.V[0], self.V[1]-v3.V[1], self.V[2]-v3.V[2])
}

func (self V3) DividedBy(v3 V3) V3 {
	return NewV3(self.V[0]/v3.V[0], self.V[1]/v3.V[1], self.V[2]/v3.V[2])
}
func (self V3) Multiply(v3 V3) V3 {
	return NewV3(self.V[0]*v3.V[0], self.V[1]*v3.V[1], self.V[2]*v3.V[2])
}

func (self V3) String() string {
	return fmt.Sprintf("%d.%d.%d", self.V[0], self.V[1], self.V[2])
}

///////////////////////

type RangeV3 struct {
	Poles [2]V3
}

func NewRangeV3(poleMin, poleMax V3) RangeV3 {
	return RangeV3{Poles: [2]V3{poleMin, poleMax}}
}

func (self RangeV3) Min() V3 {
	return self.Poles[0]
}
func (self RangeV3) Max() V3 {
	return self.Poles[1]
}

func (self *RangeV3) SetMin(v V3) {
	self.Poles[0] = v
}
func (self *RangeV3) SetMax(v V3) {
	self.Poles[1] = v
}
func (self *RangeV3) Size() V3 {
	return NewV3(
		Abs[int64](self.Poles[0].X()-self.Poles[1].X())+1,
		Abs[int64](self.Poles[0].Y()-self.Poles[1].Y())+1,
		Abs[int64](self.Poles[0].Z()-self.Poles[1].Z())+1,
	)
}
func (self *RangeV3) IsInRange(v V3) bool {
	a := v.Sub(self.Poles[0])
	b := self.Poles[1].Sub(v)
	return a.X() >= 0 && a.Y() >= 0 && a.Z() >= 0 && b.X() >= 0 && b.Y() >= 0 && b.Z() >= 0
}

///////////////////////

type PlaneType int

const (
	Plane_XY = iota
	Plane_XZ
	Plane_YZ
	Plane_Random // default
)

func rand_N1_0_1() int64 {
	d := rand.Int31n(2)
	switch d {
	case 0:
		return -1
	case 1:
		return 1
	}
	return 0
}

func rand_N1_1() int64 {
	d := rand.Int31n(2)
	switch d {
	case 0:
		return -1
	case 1:
		return 1
	}
	return 0
}

func RandNearbyV3(center V3, plane PlaneType) V3 {
	r := center
	switch plane {
	case Plane_XY:
		r.V[0] += rand_N1_1()
		r.V[1] += rand_N1_1()

	case Plane_XZ:
		r.V[0] += rand_N1_1()
		r.V[2] += rand_N1_1()

	case Plane_YZ:
		r.V[1] += rand_N1_1()
		r.V[2] += rand_N1_1()

	default:
		x := rand_N1_0_1()
		y := rand_N1_0_1()
		z := rand_N1_0_1()
		if x == 0 && y == 0 && z == 0 {
			r.V[rand.Intn(3)] += rand_N1_1()
		} else {
			r.V[0] += x
			r.V[1] += y
			r.V[2] += z
		}
	}
	return r
}
